#include"global.h"
#include"fork.h"
#include"thread.h"
#include"debug.h"
#include"string.h"
#include"file.h"
#include"bitmap.h"
#include"process.h"

extern struct file file_table[];
extern void intr_exit(void);
extern list thread_ready_list;
extern list thread_all_list;

static task_tcb* task_clone(task_tcb*);
static task_tcb* clone_task_tcb(task_tcb*);
static bool clone_task_user_space(task_tcb*,task_tcb*);
static void setup_task_kernal_stack(task_tcb*);
static void clone_task_inode(task_tcb*);

pid_t sys_fork(void){
	task_tcb* parent_process;
	task_tcb* child_process;
	
	parent_process=current_thread();
	ASSERT(parent_process->page_dir);	
	child_process=task_clone(parent_process);
	if(child_process==NULL){
		return -1;
	}
	
	ASSERT(!elem_find(&thread_ready_list,&child_process->gen_tag));
	list_append(&thread_ready_list,&child_process->gen_tag);
	ASSERT(!elem_find(&thread_all_list,&child_process->all_tag));
	list_append(&thread_all_list,&child_process->all_tag);
	return child_process->pid;
}

static task_tcb* clone_task_tcb(task_tcb* parent){
	task_tcb* child=NULL;
	child=get_page(PG_K,1);
	if(!child){
		return NULL;
	}
	memcpy(child,parent,PG_SIZE);
	strcat(child->name,"_fork");
	child->pid=alloc_pid(child->name);
	child->ppid=parent->pid;
	child->tick=child->priority;
	child->status=TASK_READY;
	child->gen_tag.next=child->gen_tag.prev=NULL;
	child->all_tag.next=child->all_tag.prev=NULL;
	return child;
}

static bool clone_task_user_space(task_tcb* child,task_tcb* parent){
	void* page_buffer;
	uint32_t byte_idx=0;
	uint32_t bit_idx;
	uint8_t* bits;
	uint32_t map_bytes_len;
	int32_t vaddr_start;
	void* vaddr;

	page_buffer=get_page(PG_K,1);
	if(!page_buffer){
		return false;
	}
	
	if(!alloc_process_pagedir(child)){
		return false;
	}

	if(!process_virtual_pool_init(child)){
		return false;
	}

	vaddr_start=parent->process_vaddr_pool.vaddr_start;
	bits=parent->process_vaddr_pool.vaddr_bitmap.bits;	
	map_bytes_len=parent->process_vaddr_pool.vaddr_bitmap.bmap_bytes_len;
	
	memcpy(child->process_vaddr_pool.vaddr_bitmap.bits \
		,parent->process_vaddr_pool.vaddr_bitmap.bits \
		,map_bytes_len);
	
	//copy user space
	while(byte_idx<map_bytes_len){
		if(bits[byte_idx]){
			bit_idx=0;
			while(bit_idx<8){
				if(bits[byte_idx] & BITMAP_MASK<<bit_idx){
					vaddr=(void*)(vaddr_start+(byte_idx*8+bit_idx)*PG_SIZE);
					memcpy(page_buffer,vaddr,PG_SIZE);
					active_page_dir(child);
					ASSERT(get_fix_clone_vpage(PG_U,(uint32_t)vaddr));
					memcpy(vaddr,page_buffer,PG_SIZE);
					active_page_dir(parent);	
				}
				bit_idx++;
			}
		}
		byte_idx++;
	}
	free_page(PG_K,page_buffer,1);
	return true;
}

static void setup_task_kernal_stack(task_tcb* child){
	intr_stack* task_intr_stack;
	task_intr_stack=(intr_stack*)((uint32_t)child+PG_SIZE-sizeof(intr_stack));
	//child task fork should return 0
	task_intr_stack->eax=0;
	uint32_t* kernal_ret_ip= (uint32_t*)task_intr_stack-1;
	*kernal_ret_ip=(uint32_t)intr_exit;
	child->k_stack=(uint32_t*)task_intr_stack-5;
}

static void clone_task_inode(task_tcb* child){
	uint32_t fd_idx;
	struct file* pf;
	for(fd_idx=3;fd_idx<TASK_MAX_FD_CNT;fd_idx++){
		if(child->fd_table[fd_idx]!=-1){
			pf=file_table+child->fd_table[fd_idx];
			pf->inode->open_cnt++;
		}
	}
}

static task_tcb* task_clone(task_tcb* parent){
	task_tcb* child=NULL;
	child=clone_task_tcb(parent);
	if(!child){
		return NULL;
	}
	if(!clone_task_user_space(child,parent)){
		return NULL;
	}
	setup_task_kernal_stack(child);
	clone_task_inode(child);
	return child;
}
